#ifndef __LOGGER_HPP__
#define __LOGGER_HPP__

#include"Mutex.hpp"
#include<iostream>
#include<ctime>
#include<string>
#include<filesystem>
#include<sstream>
#include<fstream>
#include<unistd.h>

//默认文件路径、文件名
//Linux 系统中专门存放日志的路径： /var/log
// const  std::string gdefaultdirpathname = "log";
const  std::string gdefaultdirpathname = "/var/log";
const static std::string gdefaultfilename = "log.txt";

//日志等级，并获取等级字符串
enum class LogLevel
{
    INFO,
    DEBUG,
    ERROR,
    WARNING,
    FATAL
};

std::string Level2String(LogLevel level)
{
    switch (level)
    {
        case LogLevel::INFO:
             return "Info";
        case LogLevel::DEBUG:
            return "Debug";
        case LogLevel::ERROR:
            return "Error";
        case LogLevel::WARNING:
            return "Warning";
        case LogLevel::FATAL:
            return "Fatal";
        default:
            return "Unkuown";
    }
}

//获取时间 -> 时间戳time -> localtime_r ->snprintf
std::string GetCurrentTime()
{
    time_t currtime = time(nullptr);//参数为时区，设定好了的，直接传nullptr 就可以了

    //将时间戳转换为详细的时间
    struct tm currtm;
    //  struct tm {
    //        int         tm_sec;    /* Seconds          [0, 60] */
    //        int         tm_min;    /* Minutes          [0, 59] */
    //        int         tm_hour;   /* Hour             [0, 23] */
    //        int         tm_mday;   /* Day of the month [1, 31] */
    //        int         tm_mon;    /* Month            [0, 11]  (January = 0) */
    //        int         tm_year;   /* Year minus 1900 */
    //        int         tm_wday;   /* Day of the week  [0, 6]   (Sunday = 0) */
    //        int         tm_yday;   /* Day of the year  [0, 365] (Jan/01 = 0) */
    //        int         tm_isdst;  /* Daylight savings flag */

    //        long        tm_gmtoff; /* Seconds East of UTC */
    //        const char *tm_zone;   /* Timezone abbreviation */
    //    };

    localtime_r(&currtime , &currtm);
    //利用snprintf 进行拼接
    char timebuffer[64];
    snprintf(timebuffer , sizeof(timebuffer) , "%4d-%02d-%02d %02d-%02d-%02d\n" , 
                currtm.tm_year + 1900,
                currtm.tm_mon+1,
                currtm.tm_mday , 
                currtm.tm_hour,
                currtm.tm_min,
                currtm.tm_sec);

    return timebuffer;
}

//刷新策略,使用多态的方式
class LogStrategy
{
public:
    virtual ~LogStrategy() = default;
    virtual void SyncLog(const std::string& logmessage) = 0;
};

class ConsoleLogStrategy : public LogStrategy
{
public:
    ~ConsoleLogStrategy()
    {}

    void SyncLog(const std::string& logmessage)
    {
        //向显示器输出日志信息，显示器为临界资源，需要加锁保护
        {
            LockGuard lockguard(&_lock);
            std::cout << logmessage << std::endl;
        }
    }
private:
    Mutex _lock;
};

//文件刷新策略，向文件中写入
class FileLogStrategy : public LogStrategy
{
public:
    FileLogStrategy(const std::string &pathname = gdefaultdirpathname , 
        const std::string& filename = gdefaultfilename)
    :_dir_path_name(pathname)
    ,_filename(filename)
    {
        //查看目录是否存在，没有就创建目录 -> filesystem::exeits  create_directories
        //当前目录也是临界资源，需要加锁
        {
            LockGuard lockguard(&_lock);
            if(std::filesystem::exists(_dir_path_name))
            {
                return;//存在就直接返回
            }
            //创建目录的过程也会出错，需要捕获错误
            try
            {
                std::filesystem::create_directories(_dir_path_name);
            }
            catch(const std::filesystem::filesystem_error& e)
            {
                std::cerr << e.what() << '\n';
            }
        }
    }

    void SyncLog(const std::string& logmessage)
    {
        {
            LockGuard lockguard(&_lock); //文件为临界资源，需要加锁保护
            //拼接文件路径
            std::string target = _dir_path_name;
            target +="/";
            target += _filename;

            //打开文件 -> 使用ofstream
            std::ofstream out(target.c_str() , std::ios::app);//追加的方式打开文件
            //判断文件文件是否打开成功
            if(!out.is_open())
            {
                return;
            }
            //向文件中写入日志信息
            out << logmessage <<"\n";
            //关闭文件
            out.close();
        }
    }

    ~FileLogStrategy()
    {}
private:
    //文件路径，文件名，锁
    std::string _dir_path_name;
    std::string _filename;
    Mutex _lock;
};

//获取一条完整的日志信息并刷新出去
class Logger
{
public:
    Logger()
    {}
    ~Logger()
    {}

    //指定刷新策略
    void EnableConsoleLogStrategy()
    {
        _strategy = std::make_unique<ConsoleLogStrategy>();
    }
    void EnableFileLogStrategy()
    {
        _strategy = std::make_unique<FileLogStrategy>();
    }

    //获取完整的一条日志信息
    //[时间] [日志等级] [进程pid] [文件名] [行号] - 可变参数
    class LogMessage
    {
    public:
        LogMessage(LogLevel level , const std::string& filename , int line , Logger& logger)
        :_currtime(GetCurrentTime())
        ,_level(level)
        ,_pid(getpid())
        ,_filename(filename)
        ,_line(line)
        ,_logger(logger)
        {
            //将上述信息拼接成一个字符串,利用stringstream
            std::stringstream ss;
            ss <<"[" << _currtime << "] " 
                << "[" << Level2String(_level) <<"] "
                <<"[" << _pid << "] "
                <<'[' << _filename <<"] "
                <<'[' <<_line << "] "
                <<" - ";
            _loginfo = ss.str();
        }

        //使用日志的策略
        // LOG(LogLevel) << 可变参数;
        //需要重载 <<
        template<class T>
        LogMessage& operator<<(const T& in)
        {
            std::stringstream ss;
            ss << in;
            _loginfo += ss.str();
            return *this;
        }

        //外部的日志为局部变量,即LogMessage 对象自动调用析构函数的时候，让其自动刷新出去
        ~LogMessage()
        {
            if(_logger._strategy)//刷新策略不为空就刷新
            {
                //调用指针中的SycnLog方法
                _logger._strategy->SyncLog(_loginfo);//将日志刷新出去
            }
        }

    private:
        std::string _currtime;
        LogLevel _level;
        pid_t _pid;
        std::string _filename;
        int _line;

        std::string _loginfo;//完整的日志信息
        Logger& _logger;//外部的刷新策略
    };

    //在Logger 中重载() , 返回LogMessage 对象
    LogMessage operator()(LogLevel level , const std::string& filename , int line)
    {
        return LogMessage(level , filename, line ,*this);
    }
private:
    //刷新策略是什么
    std::unique_ptr<LogStrategy> _strategy;
};

//定义全局的Logger 对象
Logger logger;
#define LOG(level) logger(level , __FILE__ , __LINE__)
//将指定刷新策略的方式也封装一下
#define EnableConsoleLogStrategy() logger.EnableConsoleLogStrategy()
#define EnableFileLogStrategy() logger.EnableFileLogStrategy()

#endif